package org.arl.modem.codec;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Random;

public class LTDecoder extends Decoder {

   protected byte[][] message;
   protected byte[][] position;
   protected int numOfMessages;
   protected int numOfSourceMessages;
   protected int numOfPackets;
   protected int sizeOfPackets;
   protected int[] numOfOnes;

   // protected int indexVal;

   /*
    * Decription: Constructor for OFG Rateless decoder Input arguments:
    * numOfMessages : Number of source messages at the encoder .This is the
    * value of k sizeOfPackets : Size of each message symbol. indexVal :
    * indexVal- temporary variable for logging activities Returns: None
    */
   public LTDecoder(int numOfMessages, int sizeOfPackets) {
      int index = 0;
      int index1 = 0;
      int index2 = 0;
      // this.indexVal = indexVal;
      this.numOfMessages = numOfMessages;
      this.sizeOfPackets = sizeOfPackets;
      numOfPackets = 0;
      numOfOnes = new int[numOfMessages];
      packet = new byte[sizeOfPackets];
      Arrays.fill(numOfOnes, (byte) 0);
      this.numOfSourceMessages = (int) Math.ceil((float) numOfMessages
            / sizeOfPackets);
      numOfMessages = this.numOfSourceMessages;
      position = new byte[numOfMessages][numOfMessages];
      message = new byte[numOfMessages][sizeOfPackets];
      for (index = 0; index < numOfMessages; index++) {
         for (index2 = 0; index2 < sizeOfPackets; index2++) {
            message[index][index2] = 0;
         }
         for (index1 = 0; index1 < numOfMessages; index1++) {
            position[index][index1] = 0;
         }
      }

   }

   /*
    * Decription: Reads a encoded packet from channel to the decoder Input
    * arguments: None Returns: None
    */
   public void readPacket() {

      ObjectInputStream in = null;
      FileInputStream inFile = null;
      FileOutputStream outFile = null;
      ObjectOutputStream out = null;
      int index = 0;
      this.header = -1;
      this.degree = -1;
      try {
         inFile = new FileInputStream("channel.dat");
         in = new ObjectInputStream(inFile);
         CodecPacket obj = new CodecPacket();
         obj = (CodecPacket) in.readObject();
         for (index = 0; index < sizeOfPackets; index++) {
            this.packet[index] = obj.packet[index];
         }
         this.header = obj.header;
         this.degree = obj.degree;
         if (in != null) {
            in.close();
         }
         outFile = new FileOutputStream("channel.dat");
         out = new ObjectOutputStream(outFile);
         for (index = 0; index < sizeOfPackets; index++) {
            obj.packet[index] = -1;
         }
         obj.header = -1;
         obj.degree = -1;
         out.writeObject(obj);

      } catch (IOException ioe) {
      } catch (ClassNotFoundException cfe) {
      } finally {
         if (out != null) {
            try {

               out.close();
            } catch (IOException ioe) {
            }
         }
      }

   }

   /*
    * Decription: Decodes the received encoded packet Input arguments: None
    * Returns: None
    */
   public void decodeMessage(CodecPacket pkt) {
      int emptyRows = numOfMessages;

      byte[] equation = new byte[numOfSourceMessages];

      int[] temp_array = new int[numOfSourceMessages];
      Arrays.fill(temp_array, 0);
      int[] temp_message = new int[sizeOfPackets];
      Random randGen = null;
      int randomNumber = 0;
      int leftMostOne = 0;
      int eqOnes = 0;
      int index = 0;
      int index1 = 0;
      int index2 = 0;
      int tempEqOne = 0;
      String fileName;
      boolean flag = false;
      long decodeStartTime = 0;
      long decodeStopTime = 0;
      float decodeTime = 0;
      this.packet = pkt.packet;
      this.header = pkt.header;
      this.degree = pkt.degree;
      PrintWriter decodeTimeFile = null;
      while (emptyRows > 0) {
         leftMostOne = 0;
         eqOnes = 0;
         randomNumber = 0;

         // readPacket();

         if (header == -1) {

            break;
         } else {
            numOfPackets++;
         }
         decodeStartTime = decodeStopTime = 0;
         decodeStartTime = System.nanoTime();

         randomNumber = header;
         int[] checkRandom = new int[degree];
         Arrays.fill(checkRandom, -1);
         Arrays.fill(equation, (byte) 0);
         randGen = new Random(randomNumber);
         for (index = 0; index < degree; index++) {
            randomNumber = randGen.nextInt(numOfSourceMessages);
            checkRandom[index] = randomNumber;
            flag = false;
            for (index1 = 0; index1 < index; index1++) {
               if (randomNumber == checkRandom[index1]) {
                  index = index - 1;
                  flag = true;
                  break;
               }
            }
            if (flag == true) {
               continue;
            }
            equation[randomNumber] = 1;
         }

         for (index = 0; index < numOfSourceMessages; index++) {
            if (equation[index] == 1) {
               leftMostOne = index;
               break;
            }
         }

         for (index = 0; index < numOfSourceMessages; index++) {
            if (equation[index] == 1) {
               eqOnes++;
            }
         }

         while (eqOnes > 0 && position[leftMostOne][leftMostOne] == 1) {

            if (eqOnes >= numOfOnes[leftMostOne]) {
               for (index = 0; index < numOfSourceMessages; index++) {
                  equation[index] = (byte) (equation[index] ^ position[leftMostOne][index]);
               }
               for (index2 = 0; index2 < sizeOfPackets; index2++) {
                  packet[index2] = (byte) (packet[index2] ^ message[leftMostOne][index2]);
               }

            } else {

               Arrays.fill(temp_array, 0);
               Arrays.fill(temp_message, 0);
               for (index = 0; index < numOfSourceMessages; index++) {
                  temp_array[index] = equation[index];
                  equation[index] = position[leftMostOne][index];
                  position[leftMostOne][index] = (byte) temp_array[index];
               }
               for (index2 = 0; index2 < sizeOfPackets; index2++) {
                  temp_message[index2] = packet[index2];
                  packet[index2] = message[leftMostOne][index2];
                  message[leftMostOne][index2] = (byte) temp_message[index2];
               }
               numOfOnes[leftMostOne] = eqOnes;

            }

            for (index = 0; index < numOfSourceMessages; index++) {
               if (equation[index] == 1) {
                  leftMostOne = index;
                  break;
               }
            }

            tempEqOne = 0;
            for (index = 0; index < numOfSourceMessages; index++) {
               if (equation[index] == 1) {
                  tempEqOne++;
               }
            }
            eqOnes = tempEqOne;

         }

         if (eqOnes > 0) {

            for (index = 0; index < numOfSourceMessages; index++) {
               position[leftMostOne][index] = equation[index];
               for (index2 = 0; index2 < sizeOfPackets; index2++) {
                  message[leftMostOne][index2] = packet[index2];
               }
            }
            emptyRows--;
            numOfOnes[leftMostOne] = eqOnes;
         }

         decodeStopTime = 0;
         decodeStopTime = System.nanoTime();

         decodeTime = 0;
         decodeTime = (float) (decodeStopTime - decodeStartTime);
         decodeTime = (float) (decodeTime * 1e-6);

         for (int index4 = 0; index4 < sizeOfPackets; index4++) {
            packet[index4] = -1;
         }
         header = -1;
         degree = -1;
         // fileName = "decodetime" + indexVal + ".dat";
         //
         // try {
         // decodeTimeFile = new PrintWriter(new FileWriter(fileName, true));
         // decodeTimeFile.print(numOfPackets);
         // decodeTimeFile.print(" ");
         // decodeTimeFile.print(decodeTime);
         //
         // decodeTimeFile.println("");
         // } catch (IOException ioe) {
         //
         // } finally {
         // if (decodeTimeFile != null) {
         // decodeTimeFile.close();
         // }
         // }

         /*
          * for (index=0;index<numOfSourceMessages;index++) { for
          * (index1=0;index1<numOfSourceMessages;index1++) {
          * System.out.print(position[index][index1]); } System.out.println("");
          * }
          */
      }
   }

   /*
    * Decription: Checks whether the OFG decoding is complete. It checks for the
    * diagonal matrix formation at the decoder which signifies that the decoding
    * is complete. Input arguments: None Returns: True if decoding is complete,
    * False if not.
    */
   public boolean DecodingSuccessful() {
      int index = 0;
      int count = 0;
      for (index = 0; index < numOfSourceMessages; index++) {
         if (position[index][index] == 1) {
            count++;
         }
      }
      if (count == numOfSourceMessages) {
         writeMessage();
         return true;
      } else {
         return false;
      }
   }

   @Override
   public byte[] getData() {
      // System.out.println("getting data as bytes for rateless!!");
      byte[] data = new byte[numOfMessages];
      byte[][] decodedMessage = new byte[numOfSourceMessages][sizeOfPackets];

      int index, index1, index2;
      index = index1 = index2 = 0;
      byte[] sum = new byte[sizeOfPackets];

      for (index = (numOfSourceMessages - 1); index >= 0; index--) {
         Arrays.fill(sum, (byte) 0);
         for (index1 = (index + 1); index1 < numOfSourceMessages; index1++) {
            for (index2 = 0; index2 < sizeOfPackets; index2++) {
               sum[index2] ^= decodedMessage[index1][index2]
                     * position[index][index1];
            }
         }
         for (index2 = 0; index2 < sizeOfPackets; index2++) {
            decodedMessage[index][index2] = (byte) ((message[index][index2] ^ sum[index2]) / position[index][index]);
         }
      }
      int noOfbytes = 0;
      for (index = 0; index < numOfSourceMessages; index++) {

         for (index2 = 0; index2 < sizeOfPackets; index2++) {
            noOfbytes++;
            if (noOfbytes <= numOfMessages)
               data[noOfbytes - 1] = (decodedMessage[index][index2]);
         }
      }
      // System.out.println("number of bytes:" + noOfbytes + ":" + data.length);
      return data;

   }

   /*
    * Decription: Writes the message symbol to the output stream byte by byte.
    * Input arguments: None Returns: None
    */
   public void writeMessage() {
      byte[][] decodedMessage = new byte[numOfSourceMessages][sizeOfPackets];

      int index, index1, index2;
      index = index1 = index2 = 0;
      byte[] sum = new byte[sizeOfPackets];

      FileOutputStream out = null;
      try {
         out = new FileOutputStream("/home/rohit/Desktop/testLT.dat");
         for (index = (numOfSourceMessages - 1); index >= 0; index--) {
            Arrays.fill(sum, (byte) 0);
            for (index1 = (index + 1); index1 < numOfSourceMessages; index1++) {
               for (index2 = 0; index2 < sizeOfPackets; index2++) {
                  sum[index2] ^= decodedMessage[index1][index2]
                        * position[index][index1];
               }
            }
            for (index2 = 0; index2 < sizeOfPackets; index2++) {
               decodedMessage[index][index2] = (byte) ((message[index][index2] ^ sum[index2]) / position[index][index]);
            }
         }
         int noOfbytes = 0;
         for (index = 0; index < numOfSourceMessages; index++) {

            for (index2 = 0; index2 < sizeOfPackets; index2++) {
               noOfbytes++;
               if (noOfbytes <= numOfMessages)
                  out.write(decodedMessage[index][index2]);
            }
         }

      } catch (IOException ioe) {
         System.out.println("file not found");
      } finally {
         if (out != null) {
            try {
               out.close();
            } catch (IOException ioe) {
            }
         }
      }
   }

   @Override
   public void writePacket(byte[] packet) {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   public boolean packetAvailable() {
      /*
       * Queue<DecoderPacket> queue=new LinkedList<DecoderPacket>(); if
       * (queue.isEmpty()) { return false; } else { return true; }
       */return false;
   }

   @Override
   public int getCodedPacketSize() {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   public CodecPacket encodeMessage() {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   public int numPacketsReceived() {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   public int readMessage() {
      throw new UnsupportedOperationException("Not supported yet.");
   }

}
